# 导入相关模块
from live_inspect.watch_cursor import WatchCursor  # 导入光标监控类
from contextlib import asynccontextmanager       # 导入异步上下文管理器
from fastmcp.utilities.types import Image        # 导入图像类型
from humancursor import SystemCursor             # 导入系统光标类
from platform import system, release             # 导入系统信息相关函数
from markdownify import markdownify              # 导入HTML转Markdown工具
from src.desktop import Desktop                  # 导入桌面操作类
from textwrap import dedent                      # 导入文本缩进处理函数
from fastmcp import FastMCP                      # 导入FastMCP框架
from typing import Literal                       # 导入字面量类型
import uiautomation as ua                        # 导入UI自动化库
import pyautogui as pg                           # 导入鼠标键盘控制库
import pyperclip as pc                           # 导入剪贴板操作库
import requests                                  # 导入HTTP请求库
import asyncio                                   # 导入异步IO库
import ctypes                                    # 导入C类型库

# 配置pyautogui
pg.FAILSAFE = False       # 关闭安全模式（鼠标移到角落不会停止程序）
pg.PAUSE = 1.0            # 每个操作后的暂停时间（秒）

# 获取操作系统信息
os = system()             # 获取操作系统名称
version = release()       # 获取操作系统版本

# 服务器说明文本
instructions = dedent(f'''
Windows MCP服务器提供直接与{os} {version}桌面交互的工具，
从而能够代表用户操作桌面。
''')

# 初始化桌面相关对象
desktop = Desktop()                          # 桌面操作对象
default_language = desktop.get_default_language()  # 获取默认语言
cursor = SystemCursor()                      # 系统光标对象
watch_cursor = WatchCursor()                 # 光标监控对象
ctypes.windll.user32.SetProcessDPIAware()    # 设置进程DPI感知（确保坐标准确）

@asynccontextmanager
async def lifespan(app: FastMCP):
    """服务器启动前运行初始化代码，关闭后运行清理代码。"""
    try:
        watch_cursor.start()                  # 启动光标监控
        await asyncio.sleep(1)                # 模拟启动延迟
        yield
    finally:
        watch_cursor.stop()                   # 停止光标监控

# 创建FastMCP实例
mcp = FastMCP(
    name='windows-mcp',
    instructions=instructions,
    lifespan=lifespan
)

@mcp.tool(
    name='Launch-Tool',
    description='通过名称从Windows开始菜单启动应用程序（例如："notepad"记事本, "calculator"计算器, "chrome"谷歌浏览器）'
)
def launch_tool(name: str) -> str:
    response, status = desktop.launch_app(name)
    return response
    
@mcp.tool(
    name='Powershell-Tool',
    description='执行PowerShell命令并返回输出结果和状态码'
)
def powershell_tool(command: str) -> str:
    response, status = desktop.execute_command(command)
    return f'状态码: {status}\n响应: {response}'

@mcp.tool(
    name='State-Tool',
    description='捕获完整的桌面状态，包括用户界面使用的默认语言、已聚焦/打开的应用程序、交互式UI元素（按钮、文本框、菜单）、信息内容（文本、标签、状态）和可滚动区域。当use_vision=True时可包含视觉截图。对于理解当前桌面上下文和可用的UI交互至关重要。'
)
def state_tool(use_vision: bool = False) -> str:
    desktop_state = desktop.get_state(use_vision=use_vision)
    interactive_elements = desktop_state.tree_state.interactive_elements_to_string()
    informative_elements = desktop_state.tree_state.informative_elements_to_string()
    scrollable_elements = desktop_state.tree_state.scrollable_elements_to_string()
    apps = desktop_state.apps_to_string()
    active_app = desktop_state.active_app_to_string()
    
    result = [dedent(f'''
    用户界面默认语言:
    {default_language}
                        
    聚焦的应用:
    {active_app}

    已打开的应用:
    {apps}

    交互式元素列表:
    {interactive_elements or '未找到交互式元素。'}

    信息性元素列表:
    {informative_elements or '未找到信息性元素。'}

    可滚动元素列表:
    {scrollable_elements or '未找到可滚动元素。'}
    ''')]
    
    # 如果需要视觉信息，添加截图
    if use_vision:
        result.append(Image(data=desktop_state.screenshot, format='png'))
        
    return result

@mcp.tool(
    name='Clipboard-Tool',
    description='复制文本到剪贴板或获取当前剪贴板内容。使用"copy"模式并提供text参数进行复制，使用"paste"模式进行获取。'
)
def clipboard_tool(mode: Literal['copy', 'paste'], text: str = None) -> str:
    if mode == 'copy':
        if text:
            pc.copy(text)  # 复制文本到系统剪贴板
            return f'已将"{text}"复制到剪贴板'
        else:
            raise ValueError("没有提供要复制的文本")
    elif mode == 'paste':
        clipboard_content = pc.paste()  # 从系统剪贴板获取文本
        return f'剪贴板内容: "{clipboard_content}"'
    else:
        raise ValueError('无效模式。请使用"copy"或"paste"。')

@mcp.tool(
    name='Click-Tool',
    description='在特定坐标点击UI元素。支持左/右/中鼠标按钮和单/双/三次点击。使用State-Tool输出中的坐标。'
)
def click_tool(loc: list[int], button: Literal['left', 'right', 'middle'] = 'left', clicks: int = 1) -> str:
    if len(loc) != 2:
        raise ValueError("位置必须是包含两个整数的列表 [x, y]")
    
    x, y = loc[0], loc[1]
    pg.moveTo(x, y)
    control = desktop.get_element_under_cursor()
    parent_control = control.GetParentControl()
    
    # 根据父控件判断点击方式
    if parent_control.Name == "Desktop":
        pg.click(x=x, y=y, button=button, clicks=clicks)
    else:
        pg.mouseDown()
        pg.click(button=button, clicks=clicks)
        pg.mouseUp()
    
    # 转换点击次数为文字描述
    num_clicks = {1: '单', 2: '双', 3: '三'}
    return f'{num_clicks.get(clicks)}击{button}键，目标是位于({x},{y})的{control.Name}元素，控件类型为{control.ControlTypeName}。'

@mcp.tool(
    name='Type-Tool',
    description='在输入框、文本区域或聚焦元素中输入文本。设置clear=True替换现有文本，False则追加。请先点击目标元素坐标。'
)
def type_tool(loc: list[int], text: str, clear: bool = False, press_enter: bool = False) -> str:
    if len(loc) != 2:
        raise ValueError("位置必须是包含两个整数的列表 [x, y]")
    
    x, y = loc[0], loc[1]
    pg.click(x=x, y=y)  # 点击目标位置
    control = desktop.get_element_under_cursor()
    
    # 如果需要清空现有内容
    if clear:
        pg.hotkey('ctrl', 'a')  # 全选
        pg.press('backspace')   # 删除
    
    # 检查文本是否包含非ASCII字符（如中文）
    if any(ord(char) > 127 for char in text):
        # 对非ASCII字符使用剪贴板方法
        original_clipboard = pc.paste()  # 保存当前剪贴板内容
        try:
            pc.copy(text)                # 将文本复制到剪贴板
            pg.sleep(0.1)                # 短暂延迟确保剪贴板更新
            pg.hotkey('ctrl', 'v')       # 粘贴
        finally:
            # 恢复原始剪贴板内容
            pg.sleep(0.1)
            pc.copy(original_clipboard)
    else:
        # 对ASCII字符使用正常输入方法
        pg.typewrite(text, interval=0.1)
    
    # 如果需要按回车键
    if press_enter:
        pg.press('enter')
    
    return f'已在位于({x},{y})的{control.Name}元素（控件类型：{control.ControlTypeName}）上输入文本：{text}。'

@mcp.tool(
    name='Resize-Tool',
    description='调整特定应用窗口（例如："notepad"记事本, "calculator"计算器, "chrome"谷歌浏览器等）的大小（宽度x高度）或移动到特定位置（X,Y）。'
)
def resize_tool(name: str, size: list[int] = None, loc: list[int] = None) -> str:
    if size is not None and len(size) != 2:
        raise ValueError("大小必须是包含两个整数的列表 [宽度, 高度]")
    if loc is not None and len(loc) != 2:
        raise ValueError("位置必须是包含两个整数的列表 [x, y]")
    
    size_tuple = tuple(size) if size is not None else None
    loc_tuple = tuple(loc) if loc is not None else None
    
    response, status = desktop.resize_app(name, size_tuple, loc_tuple)
    return response

@mcp.tool(
    name='Switch-Tool',
    description='切换到特定应用窗口（例如："notepad"记事本, "calculator"计算器, "chrome"谷歌浏览器等）并将其置于前台。'
)
def switch_tool(name: str) -> str:
    response, status = desktop.switch_app(name)
    return response

@mcp.tool(
    name='Scroll-Tool',
    description='在特定坐标或当前鼠标位置滚动。使用wheel_times控制滚动量（1格约3-5行）。对于导航列表、网页和长内容至关重要。'
)
def scroll_tool(
    loc: list[int] = None,
    type: Literal['horizontal', 'vertical'] = 'vertical',
    direction: Literal['up', 'down', 'left', 'right'] = 'down',
    wheel_times: int = 1
) -> str:
    # 如果指定了位置，移动鼠标到该位置
    if loc:
        if len(loc) != 2:
            raise ValueError("位置必须是包含两个整数的列表 [x, y]")
        x, y = loc[0], loc[1]
        pg.moveTo(x, y)
    
    # 根据类型和方向执行滚动
    if type == 'vertical':
        if direction == 'up':
            ua.WheelUp(wheel_times)
        elif direction == 'down':
            ua.WheelDown(wheel_times)
        else:
            return '无效方向。请使用"up"或"down"。'
    elif type == 'horizontal':
        if direction == 'left':
            pg.keyDown('Shift')
            pg.sleep(0.05)
            ua.WheelUp(wheel_times)
            pg.sleep(0.05)
            pg.keyUp('Shift')
        elif direction == 'right':
            pg.keyDown('Shift')
            pg.sleep(0.05)
            ua.WheelDown(wheel_times)
            pg.sleep(0.05)
            pg.keyUp('Shift')
        else:
            return '无效方向。请使用"left"或"right"。'
    else:
        return '无效类型。请使用"horizontal"或"vertical"。'
    
    return f'已{type}向{direction}滚动{wheel_times}格。'

@mcp.tool(
    name='Drag-Tool',
    description='从源坐标到目标坐标的拖放操作。用于移动文件、调整窗口大小或拖放交互。'
)
def drag_tool(from_loc: list[int], to_loc: list[int]) -> str:
    if len(from_loc) != 2:
        raise ValueError("from_loc必须是包含两个整数的列表 [x, y]")
    if len(to_loc) != 2:
        raise ValueError("to_loc必须是包含两个整数的列表 [x, y]")
    
    x1, y1 = from_loc[0], from_loc[1]
    x2, y2 = to_loc[0], to_loc[1]
    
    pg.drag(x1, y1, x2, y2, duration=0.5)
    control = desktop.get_element_under_cursor()
    
    return f'已将位于({x1},{y1})的{control.Name}元素（控件类型：{control.ControlTypeName}）拖动到({x2},{y2})。'

@mcp.tool(
    name='Move-Tool',
    description='将鼠标光标移动到特定坐标而不点击。用于悬停在元素上或在其他操作前定位光标。'
)
def move_tool(to_loc: list[int]) -> str:
    if len(to_loc) != 2:
        raise ValueError("to_loc必须是包含两个整数的列表 [x, y]")
    
    x, y = to_loc[0], to_loc[1]
    pg.moveTo(x, y)
    
    return f'已将鼠标指针移动到({x},{y})。'

@mcp.tool(
    name='Shortcut-Tool',
    description='使用组合键执行键盘快捷键。以列表形式传递键（例如：["ctrl", "c"]复制，["alt", "tab"]切换应用，["win", "r"]运行对话框）。'
)
def shortcut_tool(shortcut: list[str]):
    pg.hotkey(*shortcut)
    return f'已按下快捷键: {'+'.join(shortcut)}。'

@mcp.tool(
    name='Key-Tool',
    description='按下单个键盘按键。支持特殊键如"enter"回车, "escape"ESC, "tab"制表, "space"空格, "backspace"退格, "delete"删除, 方向键("up", "down", "left", "right"), 功能键("f1"-"f12")。'
)
def key_tool(key: str = '') -> str:
    pg.press(key)
    return f'已按下键: {key}。'

@mcp.tool(
    name='Wait-Tool',
    description='暂停执行指定的秒数。用于等待应用加载、动画完成或在操作之间添加延迟。'
)
def wait_tool(duration: int) -> str:
    pg.sleep(duration)
    return f'已等待{duration}秒。'

@mcp.tool(
    name='Scrape-Tool',
    description='获取网页内容并转换为markdown格式。提供包含协议的完整URL（http/https）。返回适合分析的结构化文本内容。'
)
def scrape_tool(url: str) -> str:
    response = requests.get(url, timeout=10)
    html = response.text
    content = markdownify(html=html)
    return f'已抓取整个网页内容:\n{content}'

# 主程序入口
if __name__ == "__main__":
    mcp.run()  # 运行MCP服务器